{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "parameters"
    ]
   },
   "outputs": [],
   "source": [
    "epochs = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 6 - Aprendizaje federado en MNIST usando una red neuronal convolucional.\n",
    "\n",
    "## Actualiza el código a aprendizaje federado en 10 líneas de Pytorch + PySyft\n",
    "\n",
    "### Contexto \n",
    "\n",
    "El aprendizaje federado es una técnica del aprendizaje automático muy emocionante y en surgimiento, que busca que sistemas aprendan con datos descentralizados. La idea es que los datos permanezcan en las manos de quienes los producen (también llamados trabajadores), lo que ayuda a mejorar la privacidad y mantener la propiedad de la data y el modelo es compartido entre los trabajadores. Una aplicación inmediata es por ejemplo predecir la siguiente palabra de un celular cuando se escribe un texto: Tú no quieres que tu información sea usada para entrenamiento - por ejemplo, tus mensajes de texto - sean enviados al servidor central.\n",
    "\n",
    "El desarrollo del aprendizaje federado está muy conectado con el aumento en la concientización de la necesidad de la privacidad en los datos y las normas de GDPR en la unión europea, en las que la UE obliga la protección de datos desde mayo de 2018. Para anticiparse a la regulación, muchos actores como apple y google han empezado a invertir masivamente en esta tecnología, especialmente para proteger la privacidad de usuarios de celulares, pero no han puesto sus herramientas disponibles.\n",
    "\n",
    "En OpenMined, creemos que todos los que podemos realizar un proyecto de aprendizaje automático también deberíamos poder implementar herramientas para preservar la privacidad con poco esfuerzo. Nosotros hemos construido herramientas para encriptar datos en una sola línea [como es mencionado en nuestro blog post](https://blog.openmined.org/training-cnns-using-spdz/) y nosotros ahora lanzamos nuestra plataforma de aprendizaje federado que usa la nueva versión de Pytorch 1.0 para proveer con una interfaz para construir modelos seguros y escalables.\n",
    "\n",
    "En este tutorial, usaremos directamente [el ejemplo canónico de entrenar una red neuronal convolucional en MNIST usando Pytorch](https://github.com/pytorch/examples/blob/master/mnist/main.py) y mostraremos lo simple que es implementar aprendizaje federado usando nuestra [libreria de PySyft](https://github.com/OpenMined/PySyft/). Iremos por cada parte del ejemplo y señalaremos el código que haya cambiado.\n",
    "\n",
    "También puedes encontrar este material en [nuestro blogpost](https://blog.openmined.org/upgrade-to-federated-learning-in-10-lines).\n",
    "\n",
    "Autores:\n",
    "- Théo Ryffel - GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "\n",
    "Traductores:\n",
    "- Carlos Salgado - Github [@socd06](https://github.com/socd06)\n",
    "- Arturo Márquez Flores - Twitter [@arturomf94](https://twitter.com/arturomf94)\n",
    "- Ricardo Pretelt - Twitter [@ricardopretelt](https://twitter.com/ricardopretelt)\n",
    "\n",
    "**Ok, ¡Empecemos!**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Librerias a importar y especificaciones del modelo.\n",
    "Primero importamos las librerías"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torchvision import datasets, transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y luego lo específico a PySyft. En particular definimos los trabajadores remotos `alice` y `bob`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import syft as sy  # <-- Nuevo: importamos la librería de Pysyft\n",
    "hook = sy.TorchHook(torch)  # <-- Nuevo: hook PyTorch.  agrega funciones para apoyar el aprendizaje federado\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")  # <-- Nuevo: Definir el trabajador remoto bob\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")  # <-- Nuevo: También definimos a alice"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Definimos la configuración del proceso de aprendizaje."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Arguments():\n",
    "    def __init__(self):\n",
    "        self.batch_size = 64\n",
    "        self.test_batch_size = 1000\n",
    "        self.epochs = epochs\n",
    "        self.lr = 0.01\n",
    "        self.momentum = 0.5\n",
    "        self.no_cuda = False\n",
    "        self.seed = 1\n",
    "        self.log_interval = 30\n",
    "        self.save_model = False\n",
    "\n",
    "args = Arguments()\n",
    "\n",
    "use_cuda = not args.no_cuda and torch.cuda.is_available()\n",
    "\n",
    "torch.manual_seed(args.seed)\n",
    "\n",
    "device = torch.device(\"cuda\" if use_cuda else \"cpu\")\n",
    "\n",
    "kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cargamos los datos y los enviamos a los trabajadores\n",
    "Nosotros cargamos los datos y transformamos el conjunto de datos de entrenamiento a otro federado dividido entre trabajadores usando el método `.federate`. Estos datos federados son enviados a un _dataloader_ federado. Los datos de prueba permanecen sin cambios.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "federated_train_loader = sy.FederatedDataLoader( # <-- esto es ahora un DataLoader federado\n",
    "    datasets.MNIST('../data', train=True, download=True,\n",
    "                   transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ]))\n",
    "    .federate((bob, alice)), # <-- Nuevo: Distribuimos los datos en todos los trabajadores, ahora son federados.\n",
    "    batch_size=args.batch_size, shuffle=True, **kwargs)\n",
    "\n",
    "test_loader = torch.utils.data.DataLoader(\n",
    "    datasets.MNIST('../data', train=False, transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ])),\n",
    "    batch_size=args.test_batch_size, shuffle=True, **kwargs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Especificaciones para la red convolucional\n",
    "Aquí usamos la misma red del ejemplo oficial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(1, 20, 5, 1)\n",
    "        self.conv2 = nn.Conv2d(20, 50, 5, 1)\n",
    "        self.fc1 = nn.Linear(4*4*50, 500)\n",
    "        self.fc2 = nn.Linear(500, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.conv1(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = F.relu(self.conv2(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = x.view(-1, 4*4*50)\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Definir las funciones de entrenamiento y de prueba\n",
    "Para la función de entrenamiento, como los lotes de datos están distribuidos por `alice` y `bob`, necesitas enviar el modelo al lugar correcto para cada lote. Entonces, realizas todas las operaciones remotamente con la misma sintaxis como si se estuviera realizando localmente en Pytorch. Cuando acabas, envías el modelo de vuelta actualizado y la pérdida obtenida para mejorar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(args, model, device, federated_train_loader, optimizer, epoch):\n",
    "    model.train()\n",
    "    for batch_idx, (data, target) in enumerate(federated_train_loader): # <-- Ahora son datos distribuidos\n",
    "        model.send(data.location) # <-- Nuevo: Enviar el modelo a la ubicación correcta\n",
    "        data, target = data.to(device), target.to(device)\n",
    "        optimizer.zero_grad()\n",
    "        output = model(data)\n",
    "        loss = F.nll_loss(output, target)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        model.get() # <-- Nuevo: Enviar el modelo de vuelta\n",
    "        if batch_idx % args.log_interval == 0:\n",
    "            loss = loss.get() # <-- Nuevo: Enviar las pérdidas de vuelta\n",
    "            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n",
    "                epoch, batch_idx * args.batch_size, len(federated_train_loader) * args.batch_size,\n",
    "                100. * batch_idx / len(federated_train_loader), loss.item()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La función de prueba no cambia!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(args, model, device, test_loader):\n",
    "    model.eval()\n",
    "    test_loss = 0\n",
    "    correct = 0\n",
    "    with torch.no_grad():\n",
    "        for data, target in test_loader:\n",
    "            data, target = data.to(device), target.to(device)\n",
    "            output = model(data)\n",
    "            test_loss += F.nll_loss(output, target, reduction='sum').item() # sumar las pérdidas por lote\n",
    "            pred = output.argmax(1, keepdim=True) # Obtener el índice de la máxima probabilidad logarítmica \n",
    "            correct += pred.eq(target.view_as(pred)).sum().item()\n",
    "\n",
    "    test_loss /= len(test_loader.dataset)\n",
    "\n",
    "    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n",
    "        test_loss, correct, len(test_loader.dataset),\n",
    "        100. * correct / len(test_loader.dataset)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Lanza el entrenamiento!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "model = Net().to(device)\n",
    "optimizer = optim.SGD(model.parameters(), lr=args.lr) # Momentum no está disponible en el momento\n",
    "\n",
    "for epoch in range(1, args.epochs + 1):\n",
    "    train(args, model, device, federated_train_loader, optimizer, epoch)\n",
    "    test(args, model, device, test_loader)\n",
    "\n",
    "if (args.save_model):\n",
    "    torch.save(model.state_dict(), \"mnist_cnn.pt\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et voilà! Aquí está, has entrenado un modelo con datos remotos usando aprendizaje federado!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Para terminar\n",
    "Sé que hay una pregunta que te mueres por realizar: **¿Cuánto tiempo toma realizar aprendizaje federado comparado con PyTorch normal?**\n",
    "El tiempo de computación es **menos del doble del tiempo** usado para una ejecución normal de PyTorch! Más precisamente, toma 1.9 veces más tiempo, lo que es pequeño comparado con las características que pudimos agregar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusión\n",
    "\n",
    "Como puedes observar, modificamos 10 líneas de código para actualizar el ejemplo oficial de Pytorch en MNIST a una configuración real de ¡aprendizaje federado!\n",
    "\n",
    "Claro, existen una docena de mejoras que se pueden pensar. Nos gustaría que la computación se operara en paralelo de los trabajadores y realizar un promedio federado, actualizar el modelo central cada `n` lote solamente, reducir el número de mensajes usados para comunicar entre los trabajadores, etc. Estas son características en que estamos trabajando para hacer aprendizaje federado listo para producción y escribiremos sobre ellos tan pronto al estar disponibles.\n",
    "\n",
    "Ahora deberías poder hacer aprendizaje federado por ¡tí mismo! Si disfrutaste esto, y te gustaría unirte al movimiento para preservar la privacidad, la propiedad descentralizada de AI y la cadena de suministro de AI (los datos), puedes hacerlo de ¡las siguientes formas!\n",
    " \n",
    "\n",
    "### Dale una estrella a PySyft en GitHub\n",
    "\n",
    "¡La forma más fácil de ayudar a nuestra comunidad es por darle estrellas a los repositorios de Github! Esto ayuda a crear consciencia de las interesantes herramientas que estamos construyendo.\n",
    "\n",
    "- [Estrella a PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Usa nuestros tutoriales de GitHub!\n",
    "\n",
    "Hicimos muy buenos tutoriales  para conseguir un mejor entendimiento de lo que aprendizaje federado y aprendizaje que preserva la privacidad debe ser y cómo construimos los bloques para que esto pase.\n",
    "\n",
    "- [Mirar los tutoriales de PySyft](https://github.com/OpenMined/PySyft/tree/master/examples/tutorials)\n",
    "\n",
    "### ¡Únete a nuestro Slack!\n",
    "\n",
    "La mejor manera de mantenerte actualizado con los últimos avances es ¡unirte a la comunidad!\n",
    "\n",
    "- [Unirse a slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### ¡Únete a un proyecto de código!\n",
    "\n",
    "La mejor manera de contribuir a nuestra comunidad es convertirte en un ¡contribuidor de código! En cualquier momento puedes ir al Github Issues de PySyft y filtrar por \"Proyectos\". Esto mostrará todos los tiquetes de nivel superior dando un resumen de los proyectos a los que ¡te puedes unir! Si no te quieres unir a un proyecto, pero quieres hacer un poco de código, también puedes mirar más mini-proyectos \"de una persona\" buscando por Github Issues con la etiqueta `Good First Issue`.\n",
    "\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Donar\n",
    "\n",
    "\n",
    "Si no tienes tiempo para contribuir a nuestra base de código, pero quieres ofrecer tu ayuda, también puedes aportar a nuestro Open Collective\". Todas las donaciones van a nuestro web hosting y otros gastos de nuestra comunidad como ¡hackathons y meetups!\n",
    "\n",
    "- [Donar a través de la página de OpenMined](https://opencollective.com/openmined)"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
